//
// Created by LiuYou on 2021/8/5.
//

#include <cstdio>
#include <cstdlib>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctime>
#include <sys/wait.h>
#include <sys/mman.h>
#include <cerrno>
#include "Protocol.hpp"


#define SIG_NOTIFY          SIGUSR2


enum {
    STATE_IDLE = 0,
    STATE_BUSY,

};


class Server {
public:
    pid_t pid;
    int state;
//    int reuse;

};


static const int MIN_SPARE_SERVER = 5;
static const int MAX_SPARE_SERVER = 10;
static const int MAX_CLIENT_NUM = 20;


static Server* serverPool;
static int idleCount = 0;
static int busyCount = 0;
static int socketFd;


static void serverJob(int pos) {
    pid_t ppid = getppid();
    while (true) {
        serverPool[pos].state = STATE_IDLE;
        kill( ppid, SIG_NOTIFY );

        struct sockaddr_in remoteAddress;
        socklen_t addressLen = sizeof( remoteAddress );
        int acceptFd = accept( socketFd, (sockaddr*) &remoteAddress, &addressLen );
        if (acceptFd < 0) {
            if (errno != EINTR || errno != EAGAIN) {
                perror( "accept()" );
                exit( 1 );
            }
        }

        serverPool[pos].state = STATE_BUSY;
        kill( ppid, SIG_NOTIFY );

        const int IP_STR_SIZE = 40;
        char ipStr[IP_STR_SIZE];
        inet_ntop( AF_INET, &remoteAddress.sin_addr, ipStr, IP_STR_SIZE );
//        printf( "[%d] client:%s:%d\n", getpid(), ipStr, ntohs( remoteAddress.sin_port ));

        time_t stamp = time( nullptr );
        const int LINE_BUF_SIZE = 80;
        char lineBuf[LINE_BUF_SIZE];
        int len = snprintf( lineBuf, LINE_BUF_SIZE, FMT_STAMP, stamp );
        send( acceptFd, lineBuf, len, 0 );
        sleep( 5 );
        close( acceptFd );
    }
}

static int addOneServer() {
    // FIXME: 解决了, 这个bug真的不好找, WC!!!!
    if (idleCount + busyCount >= MAX_CLIENT_NUM) {
        return -1;
    }
    int i;
    for (i = 0; i < MAX_CLIENT_NUM; ++i) {
        if (serverPool[i].pid == -1) {
            break;
        }
    }

    serverPool[i].state = STATE_IDLE;
    pid_t pid = fork();
    if (pid < 0) {
        perror( "fork()" );
        exit( 1 );
    }
    if (pid == 0) {
        serverJob( i );
        exit( 0 );
    } else {
        serverPool[i].pid = pid;
        ++idleCount;
    }
    return 0;
}

static int removeOneServer() {
    if (idleCount == 0) {
        return -1;
    }
    for (int i = 0; i < MAX_CLIENT_NUM; ++i) {
        if (serverPool[i].pid != -1 && serverPool[i].state == STATE_IDLE) {
            kill( serverPool[i].pid, SIGTERM );
            serverPool[i].pid = -1;
            --idleCount;
            break;
        }
    }
    return 0;
}

static int scanPool() {
    int busy = 0;
    int idle = 0;
    for (int i = 0; i < MAX_CLIENT_NUM; ++i) {
        if (serverPool[i].pid == -1) {
            continue;
        }
        if (kill( serverPool[i].pid, 0 )) {
            serverPool[i].pid = -1;
            continue;
        }
        if (serverPool[i].state == STATE_IDLE) {
            ++idle;
        } else if (serverPool[i].state == STATE_BUSY) {
            ++busy;
        } else {
            fprintf( stderr, "Unknown state.\n" );
//            _exit( 1 );
            abort();
        }
    }
    idleCount = idle;
    busyCount = busy;
    return 0;
}

static void usr2Handle(int sig) {
    return;
}

int main() {
    // TODO 注意: !!! 这里注意 现在 sigaction结构体 已经没有 sa_handler 这个字段了。
    // TODO 注意: !!! 是有的, 现在变成宏了。只是CLion不提示。
    struct sigaction sigAction;

    sigAction.sa_handler = SIG_IGN;
    sigemptyset( &sigAction.sa_mask );
    sigAction.sa_flags = SA_NOCLDWAIT;

    struct sigaction oldSigAction;
    sigaction( SIGCHLD, &sigAction, &oldSigAction );

    sigAction.sa_handler = usr2Handle;
    sigemptyset( &sigAction.sa_mask );
    sigAction.sa_flags = 0;
    sigaction( SIG_NOTIFY, &sigAction, &oldSigAction );

    void* pVoid = mmap( nullptr, sizeof( Server ) * MAX_CLIENT_NUM,
                        PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
                        -1, 0 );
    serverPool = static_cast<Server*>(pVoid);
    if (serverPool == MAP_FAILED) {
        perror( "mmap()" );
        exit( 1 );
    }

    for (int i = 0; i < MAX_CLIENT_NUM; ++i) {
        serverPool[i].pid = -1;
    }

    socketFd = socket( AF_INET, SOCK_STREAM, 0 );
    if (socketFd < 0) {
        perror( "socket()" );
        exit( 1 );
    }

    int optVal = 1;
    if (setsockopt( socketFd, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof( optVal )) < 0) {
        perror( "setsockopt()" );
        exit( 1 );
    }

    struct sockaddr_in localAddress{
            .sin_family=AF_INET,
            .sin_port=htons( atoi( SERVER_PORT ))
    };
    inet_pton( AF_INET, "0.0.0.0", &localAddress.sin_addr );
    int ret = bind( socketFd, (sockaddr*) &localAddress, sizeof( localAddress ));
    if (ret < 0) {
        perror( "bind()" );
        exit( 1 );
    }

    ret = listen( socketFd, 100 );
    if (ret < 0) {
        perror( "listen()" );
        exit( 1 );
    }

    for (int i = 0; i < MIN_SPARE_SERVER; ++i) {
        addOneServer();
    }

    sigset_t set;
    sigset_t oldSet;
    sigemptyset( &set );
    sigaddset( &set, SIG_NOTIFY );
    sigprocmask( SIG_BLOCK, &set, &oldSet );
    while (true) {
        sigsuspend( &oldSet );
        scanPool();

        if (idleCount > MAX_SPARE_SERVER) {
            for (int i = 0; i < (idleCount - MAX_SPARE_SERVER); ++i) {
                removeOneServer();
            }
        } else if (idleCount < MIN_SPARE_SERVER) {
            for (int i = 0; i < (MIN_SPARE_SERVER - idleCount); ++i) {
                addOneServer();
            }
        }

        // control pool(printf pool)
        for (int i = 0; i < MAX_CLIENT_NUM; ++i) {
            if (serverPool[i].pid == -1) {
                putchar( ' ' );
            } else if (serverPool[i].state == STATE_IDLE) {
                putchar( '.' );
            } else {
                putchar( 'x' );
            }
        }
        putchar( '\n' );

    }
    sigprocmask( SIG_SETMASK, &oldSet, nullptr );


    exit( 0 );
}





